現在已經有兩個關鍵元件齊備:新型態的代理人(ActionClient),它和 server 之間有不同的狀態機來回,而且已經可以接起來。有趣的是,我並非完全採用隨機選點的方式,而是一律從所有選點當中持續從頭到尾的順序反覆嘗試,這會讓它的勝率比隨機猴子還差,不過無所謂,目的是有這個可以遊玩的狀態機。第二個元件是網頁邏輯和基本的輸入資訊,來自棋譜閱讀器,已經可以在瀏覽器的 console 觀察到不同座標的點擊,但是還不確定這個資訊怎麼傳回到 Rust 的部份。
看似準備蠻齊全了,但是說實在話還是不知道怎麼整合。主要是後者,因為要造出一個 HTTP 伺服器的緣故,有個卡住等待的邏輯,
// Start the service
HttpServer::new(move || {
App::new()
.app_data(setup_state.clone())
.route("/", web::get().to(index))
.route("/game_state", web::get().to(game_state))
.service(fs::Files::new("/static", "static").show_files_listing())
})
.bind("127.0.0.1:8080")?
.run()
.await
但因為我們的伺服器可以主持多場遊戲,所以我希望這些展開伺服器、連上網頁遊玩、結束遊戲、再啟新局的過程,可以安插在新的代理人的某些迴圈裡面。對於 Rust 的 actix
非同步框架毫不熟悉的我,於是直接走上詠唱之道。
You are a Rust hacker and you are implementing a simple framework according to my spec. The spec is rough, but the rule of thumb is as simple as possible.
Context:
What I want you to implement:
Note that the code you give me will compile without problem, so you have to give me a well-crafted and well-tested Cargo.toml manifest as well. The simple framework will be brief yet comprehensive, and you expect me to smoothly integrate this framework into what I currently have.
Thank you in advance.
儘管耳提面命,它還是造成了一些錯誤,但是加一加套件與宣告,就整組動起來了。有興趣的讀者可以直接抄過去用:
chess_web_interface/
├── Cargo.toml
└── src
├── main.rs
└── web_server.rs
└── static
├── index.html
└── app.js
[package]
name = "fake"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4"
actix-files = "0.6.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
// src/main.rs
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
use std::time::Duration;
mod web_server;
use web_server::start_web_server;
fn main() {
// Channel to send reload signals to the web server
let (reload_tx, reload_rx): (Sender<()>, Receiver<()>) = mpsc::channel();
// Channel to receive click coordinates from the web server
let (click_tx, click_rx): (Sender<(u32, u32)>, Receiver<(u32, u32)>) = mpsc::channel();
// Start the web server in a separate thread
let web_server_handle = thread::spawn(move || {
start_web_server(reload_rx, click_tx);
});
// Main loop to simulate multiple games
for game_number in 1..=5 {
println!("Starting Game {}", game_number);
// Simulate game duration
let game_duration = Duration::from_secs(10);
let start_time = std::time::Instant::now();
loop {
// Check if the game duration has passed
if start_time.elapsed() >= game_duration {
println!("Game {} ended.", game_number);
// Signal the web server to reload for the next game
reload_tx.send(()).expect("Failed to send reload signal");
break;
}
// Check for any click events from the web interface
if let Ok((x, y)) = click_rx.try_recv() {
println!("Received click at ({}, {}) from web interface.", x, y);
// Here you can handle the click coordinates as needed
}
// Sleep briefly to avoid busy waiting
thread::sleep(Duration::from_millis(100));
}
// Wait before starting the next game
thread::sleep(Duration::from_secs(2));
}
// After all games, you can shutdown the web server if needed
// For simplicity, we'll just exit
println!("All games completed. Shutting down.");
// Note: In a real application, you would implement a graceful shutdown
// mechanism for the web server.
// For this mock, we'll simply exit.
}
// src/web_server.rs
use actix_files as fs;
use actix_web::rt::System;
use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
#[derive(Deserialize)]
struct ClickData {
x: u32,
y: u32,
}
// Handler for receiving click coordinates
#[post("/click")]
async fn receive_click(data: web::Json<ClickData>, click_tx: web::Data<Sender<(u32, u32)>>) -> impl Responder {
let x = data.x;
let y = data.y;
// Send the coordinates back to main.rs
if let Err(e) = click_tx.send((x, y)) {
eprintln!("Failed to send click data to main: {}", e);
return HttpResponse::InternalServerError().body("Failed to process click");
}
HttpResponse::Ok().body("Click received")
}
pub fn start_web_server(reload_rx: Receiver<()>, click_tx: Sender<(u32, u32)>) {
// Clone the click_tx to move into the Actix-web data
let click_tx_data = web::Data::new(click_tx);
// Start the Actix system
let server = HttpServer::new(move || {
App::new()
.app_data(click_tx_data.clone())
.service(receive_click)
.service(fs::Files::new("/", "./static").index_file("index.html"))
})
.bind(("127.0.0.1", 8080))
.expect("Can not bind to port 8080")
.run();
// Handle reload signals in a separate thread
let reload_handle = thread::spawn(move || {
for _ in reload_rx {
println!("Reload signal received. Reloading the web interface...");
// Implement the reload logic here.
// For simplicity, we'll print a message.
// In a real application, you might notify the frontend to reload via WebSockets
// or another mechanism.
// Alternatively, you could restart the server, but that's more involved.
}
});
// Run the server (this blocks until the server is stopped)
let server_handle = thread::spawn(move || {
let sys = actix_web::rt::System::new();
sys.block_on(server)
});
// Wait for both threads to finish
server_handle.join().expect("Server thread panicked");
reload_handle.join().expect("Reload thread panicked");
}
<!-- static/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Chess Web Interface</title>
<style>
#grid {
display: grid;
grid-template-columns: repeat(2, 100px);
grid-template-rows: repeat(2, 100px);
gap: 10px;
margin: 50px auto;
width: max-content;
}
.cell {
background-color: #f0f0f0;
border: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 24px;
}
.cell:hover {
background-color: #e0e0e0;
}
</style>
</head>
<body>
<h1 style="text-align: center;">Chess Web Interface</h1>
<div id="grid">
<div class="cell" data-x="0" data-y="0">A1</div>
<div class="cell" data-x="1" data-y="0">B1</div>
<div class="cell" data-x="0" data-y="1">A2</div>
<div class="cell" data-x="1" data-y="1">B2</div>
</div>
<script src="app.js"></script>
</body>
</html>
// static/app.js
document.addEventListener('DOMContentLoaded', () => {
const cells = document.querySelectorAll('.cell');
cells.forEach(cell => {
cell.addEventListener('click', () => {
const x = parseInt(cell.getAttribute('data-x'));
const y = parseInt(cell.getAttribute('data-y'));
fetch('/click', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ x, y }),
})
.then(response => response.text())
.then(data => {
console.log('Server response:', data);
})
.catch((error) => {
console.error('Error:', error);
});
});
});
});
真的會顯示這個可以按的樣子!然後也的確可以看到 Rust 端能夠得到相關資訊,如:
實在是...太好用了。所以接下來就是用這套串接的方法來看怎麼串我原本有的東西囉。